import Factory from "../core/factory";
import Layers from "../layers/index";

/**
 * 数据字典
 *
 * 内部封装了数据字典相关的所有逻辑
 *
 * 想分离工厂类与字典之间的耦合，但是暂时没有特别好的思路
 */
class Dictionary {
    /**
     * 数据字典元素
     *
     * @private
     */
    _list = [];

    /**
     * 默认的样式 class-name
     * @private
     */
    _defaultStyle = '';

    /**
     * 数据字典
     *
     * @param list - 字典列表（格式化好的数据字典）
     * @param style - 默认样式
     */
    constructor(list = [], style = '') {
        this._list = list;
        this._defaultStyle = style;
    }

    get list() {
        return this._list;
    }

    set list(value) {
        this._list = value;
    }

    get defaultStyle() {
        return this._defaultStyle;
    }

    set defaultStyle(value) {
        this._defaultStyle = value;
    }

    /**
     * 将自身添加到缓存
     *
     * @param key {string}
     */
    registerAs(key) {
        Dictionary.register(key, this);
    }

    /**
     * 获取匹配的数据字典
     *
     * @param value - 值
     * @param compare - 比较函数
     * @returns {DictionaryItem | undefined} 数据字典
     */
    find(value, compare) {
        const options = this._list;
        if (Layers.isNotEmpty(options)) {
            for (let i = 0; i < options.length; i++) {
                if (compare(options[i].value, value)) {
                    return options[i];
                }
            }
        }
    }

    /**
     * 获取匹配的数据字典
     *
     * @param value - 值
     * @returns 数据字典
     */
    findByValue(value) {
        const options = this._list;
        if (Layers.isNotEmpty(options)) {
            for (let i = 0; i < options.length; i++) {
                if (options[i].value === value) {
                    return options[i];
                }
            }
        }
    }

    /**
     * 按照区间进行数据字典匹配
     *
     * @param value - 值
     * @returns 数据字典
     */
    findByRange(value) {
        return this.find(value, Layers.isInRange);
    }

    /**
     * 获取匹配的数据字典
     *
     * @param value - 值
     * @param label - 默认标签
     * @param style - 默认样式
     * @returns 数据字典的拷贝
     */
    matchByValue(value, label = '', style = '') {
        const item = this.findByValue(value);
        if (item !== undefined) {
            return {label: item.label, style: item.style, value: value, ref: item.ref};
        } else {
            const l = label ? label : value;
            const s = style ? style : this._defaultStyle;
            return {label: l, style: s, value: value};
        }
    }

    /**
     * 获取匹配的数据字典
     *
     * @param value - 值
     * @param label - 默认标签
     * @param style - 默认样式
     * @returns 数据字典的拷贝
     */
    matchByRange(value, label = '', style = '') {
        const item = this.findByRange(value);
        if (item !== undefined) {
            return {label: item.label, style: item.style, value: value, ref: item.ref};
        } else {
            const l = label ? label : value;
            const s = style ? style : this._defaultStyle;
            return {label: l, style: s, value: value};
        }
    }

    /**
     * 获取匹配的数据字典标签
     *
     * @param value - 值
     * @param def  - 默认值
     * @returns  - 数据字典标签
     */
    getLabelByValue(value, def = undefined) {
        let dic = this.findByValue(value);
        if (Layers.isNotBlank(dic)) {
            return dic.label;
        } else {
            return def ? def : value;
        }
    }

    // ------------------------------------- static start ---------------------------------------

    /**
     * 数据字典哑元元素
     *
     * 空的数据字典，用语占位，避免空值问题
     */
    static DEF = new Dictionary();

    /**
     * 数据字典缓存
     *
     * 一次会话中，不重复查询字典
     */
    static cache = new Factory();

    /**
     * 前端直接定义的字典
     *
     * 如果 provider 中可以获取到同名字典，则当前字典会被逐渐清除
     */
    static defined = new Factory();

    /**
     * 数据字典查询函数
     *
     * @type {(key: string) => Promise<Dictionary>}
     */
    static provider;

    /**
     * 获取数据字典
     *
     * 多数数据字典用的是这个数据字典加载器
     *
     * 如果未获取到数据字典，则返回 undefined，因为是异步载入，因此必须注意页面渲染的生命周期
     *
     * @param key {string}             字典名称
     * @returns {Promise<Dictionary>}  数据字典
     */
    static query(key) {
        // 如果缓存中存在，直接返回
        const cached = this.get(key);
        if (cached !== undefined) return Promise.resolve(cached);

        return this.provider(key).then(ret => {
            if (ret !== undefined) {
                // cache the dictionary
                this.put(key, ret);

                // defined in other ways, clear defined-dictionary
                // 如果字典存在其它来源，清除前端直接定义的字典
                this.defined.remove(key);

                // resolve
                return ret;
            } else {
                // using defined-cache
                const defined = this.defined.get(key);
                return defined !== undefined ? defined : Dictionary.DEF;
            }
        }).catch(e => {
            console.error(`query dictionary failed: ${key}`, e);
            return Dictionary.DEF;
        });
    }

    /**
     * 添加一个前端定义的字典
     *
     * @param key {string}      唯一指向数据字典的 ID
     * @param dic {Array<DictionaryItem> | Dictionary}  数据字典
     * @return {Dictionary | undefined} old value
     */
    static register(key, dic) {
        if (Array.isArray(dic)) {
            dic = new Dictionary(dic);
        }
        return this.defined.put(key, dic);
    }

    /**
     * 清除所有缓存中的数据字典
     */
    static clear() {
        this.cache.clear();
    }

    /**
     * 添加字典到缓存
     *
     * @param key {string}      唯一指向数据字典的 ID
     * @param dic {Dictionary}  数据字典
     * @return {Dictionary | undefined} old value
     */
    static put(key, dic) {
        return this.cache.put(key, dic);
    }

    /**
     * 从缓存获取字典
     *
     * @param key {string}      唯一指向数据字典的 ID
     * @returns {Dictionary | undefined}    数据字典
     */
    static get(key) {
        return this.cache.get(key);
    }

    /**
     * 将数组格式化为数据字典
     *
     * @param list  - 任意数组
     * @param label - 字段名
     * @param value - 字段值
     */
    static from(list, label, value) {
        const arr = Layers.wrapArray(list, (ele) => {
            return {label: ele[label], value: ele[value], ref: ele}
        });

        return new Dictionary(arr);
    }
}

export default Dictionary;